#pragma once
#include "base/function.h"

namespace myrpc::base
{
  namespace detail
  {

    template <typename... Datas>
    struct Tuple
    {
    };

    template <typename T, typename... Datas>
    struct Tuple<T, Datas...> : Tuple<Datas...>
    {
      using Type = T;
      using Type_ptr = T *;
      using Base = Tuple<Datas...>;
    };

    template <typename T_>
    struct Traits_Ret_Arg : public Traits_Ret_Arg<decltype(&T_::operator())>
    {
      using T = T_;
    };

    template <typename Lambda, typename Ret_, typename... Args_>
    struct Traits_Ret_Arg<Ret_ (Lambda::*)(Args_...) const>
    {
      using Ret_Args = Tuple<Ret_, Args_...>;
    };

    template <typename Ret_, typename... Args_>
    struct Traits_Ret_Arg<Ret_ (*)(Args_...)>
    {
      using T = Ret_ (*)(Args_...);
      using Ret_Args = Tuple<Ret_, Args_...>;
    };

    template <typename Ret_, typename... Args_>
    struct Traits_Ret_Arg<Ret_(Args_...)>
    {
      using T = Ret_ (*)(Args_...);
      using Ret_Args = Tuple<Ret_, Args_...>;
    };

  } // namespace detail

  template <size_t Buffer_size = 64>
  class Rpccall
  {
  public:
    using Con = net_tools::net::Connect;
    using final_call = void (*)(Rpccall *, char *);
    using final_call_ret = void (*)(Rpccall *, char *, Con *);
    Rpccall()
        : func_body_(nullptr),
          cb_(nullptr),
          final_call_func_(nullptr),
          final_call_ret_func_(nullptr){};
    ~Rpccall(){};

    template <typename Ret, typename T, typename... Args>
    void Init(detail::Tuple<Ret, Args...>, T func_body, rpc_callback cb)
    {
      using Type = typename std::decay<T>::type;
      cb_ = std::move(cb);
      static_assert(sizeof(Type) <= Buffer_size, "function_body too big");
      final_call_func_ = [](Rpccall<> *this_, char *buf)
      {
        Rpccall::Call<Ret, Args...>(*reinterpret_cast<T *>(&this_->buffer_), buf);
      };
      if constexpr (std::is_void<Ret>::value)
        final_call_ret_func_ = [](Rpccall<> *this_, char *buf, Con *con)
        {
          Rpccall::Call<void, Args...>(*reinterpret_cast<T *>(&this_->buffer_), buf);
          this_->cb_(con, nullptr, 0);
        };
      else
        final_call_ret_func_ = [](Rpccall<> *this_, char *buf, Con *con)
        {
          Ret ret = Rpccall::Call<Ret, Args...>(*reinterpret_cast<T *>(&this_->buffer_), buf);
          this_->cb_(con, reinterpret_cast<void *>(&ret), sizeof(Ret));
        };

      auto alloc = [](void *buffer_ptr, void *object_ptr)
      {
        Type *body = static_cast<Type *>(object_ptr);
        new (buffer_ptr) Type(std::move(*body));
      };
      alloc(&buffer_, &func_body);
    };

    operator bool()
    {
      return final_call_func_;
    };

    void Final_call(char *buf)
    {
      final_call_func_(this, buf);
    };
    void Final_call_ret(char *buf, Con *con)
    {
      final_call_ret_func_(this, buf, con);
    };

  private:
    union
    {
      typename std::aligned_storage<Buffer_size, sizeof(size_t)>::type buffer_;
      void *func_body_;
    };
    rpc_callback cb_;
    final_call final_call_func_;
    final_call_ret final_call_ret_func_;

  public:
    template <typename Ret, typename... Args, typename T>
    static Ret Call(T &&func_body, char *buf)
    {
      return Call<Ret>(std::forward<T>(func_body), detail::Tuple<Args...>(), buf);
    };

    template <typename... Datas>
    static unsigned int Make(unsigned int type, char *buf, bool ret, Datas &&...datas)
    {
      unsigned int *temp = reinterpret_cast<unsigned int *>(buf);
      *temp = type;
      buf += sizeof(unsigned int);
      unsigned int *size = reinterpret_cast<unsigned int *>(buf);
      buf += sizeof(unsigned int);
      *buf = ret;
      buf += sizeof(bool);
      *size = (Make(buf, std::forward<Datas>(datas)) + ...);
      return 9 + *size;
    };

  private:
    template <typename Ret, typename Func, typename Tuple, typename... Datas>
    static Ret Call(Func &&func, Tuple, char *buf, Datas &&...args)
    {
      typename Tuple::Type data = *reinterpret_cast<typename Tuple::Type_ptr>(buf);
      buf += sizeof(typename Tuple::Type);
      return Call<Ret>(std::forward<Func>(func), typename Tuple::Base(), buf, std::forward<Datas>(args)..., std::move(data));
    };
    template <typename Ret, typename Func, typename... Datas>
    static Ret Call(Func &&func, detail::Tuple<>, char *buf, Datas &&...args)
    {
      return func(args...);
    };

    template <typename Data>
    static unsigned int Make(char *&buf, Data &&data)
    {
      typename std::decay<Data>::type &temp = *reinterpret_cast<typename std::decay<Data>::type *>(buf);
      temp = data;
      buf += sizeof(typename std::decay<Data>::type);
      return sizeof(typename std::decay<Data>::type);
    };

    constexpr static unsigned int Make(char *buf)
    {
      return 0;
    };
  };

} // namespace myrpc::base
